#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include "common.h"
#include <stdio.h>
#include <windows.h>
#include<time.h>

/** 海康sdk头文件 */
#include "HCNetSDK.h"

static napi_threadsafe_function ts_fn;        //线程回调句柄
static napi_ref                 cb_function;  //结束回调

typedef struct{
    int64_t  playHandle;
    char*    pBuffer;
    size_t   dwBufSize;
} data_info;

void CALLBACK g_RealDataCallBack_V30(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer,DWORD dwBufSize,DWORD dwUser)
{
    printf("call back dataType:%d\n",dwDataType);
    switch (dwDataType)
    {
    case NET_DVR_STREAMDATA:   //码流数据
        if (dwBufSize > 0)
        {
            data_info* video_data  = (data_info*)malloc(sizeof(data_info));
			video_data->playHandle = (int64_t)dwUser;
            video_data->pBuffer    = (char*)pBuffer;
            video_data->dwBufSize  = (size_t)dwBufSize;

            napi_status status = napi_call_threadsafe_function(ts_fn, video_data, napi_tsfn_nonblocking);
            printf("call back status:%d, size:%d\n", status, dwBufSize);
        }
        break;
    }
}

void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
    char tempbuf[256] = {0};
    printf("dwType:%d", dwType);
    switch(dwType) 
    {
    case EXCEPTION_RECONNECT:    //预览时重连
        printf("----------reconnect--------%lld\n", time(NULL));
        break;
    default:
        break;
    }
}

// napi_create_external_buffer中的释放函数
static void freeVideoBuff(napi_env env, void* data, void* finalize_hint) {
  NAPI_ASSERT_RETURN_VOID(env, data != NULL, "invalid data");
  (void)finalize_hint;
  free(data);
}

// 回调js的方法，如果不设置这个函数，回调js将不带参数
static void call_js(napi_env env, napi_value cb, void* hint, void* data) {
    if(data == NULL) 
        return;
	data_info*  video_data = (data_info*)data;
    int64_t     playHandle = video_data->playHandle;
    char*       pBuffer    = video_data->pBuffer;
    size_t      dwBufSize  = video_data->dwBufSize;
    free(video_data);

  if (!(env == NULL || cb == NULL)) {
    napi_value argv[2], undefined;
    NAPI_CALL_RETURN_VOID(env, napi_create_int64(env, playHandle, &argv[0]));
    NAPI_CALL_RETURN_VOID(env, napi_create_external_buffer(env, dwBufSize, pBuffer, freeVideoBuff, NULL, &argv[1]));
    NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
    NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, cb, 2, argv, NULL));
  }
}

//线程结束时的调用
static void final_fuc(napi_env env, void *data, void *hint) {
    printf("final_fuc\n");
    napi_value js_cb, undefined;
    NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, cb_function, &js_cb));
    NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
    NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
    NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, cb_function));
}

/***
 * function Node_DVR_Init(cb, fcb)
 * cb: function(playHandle, buff) 视频回调
 * fcb: function() 结束回调
 * return null
 * */
static napi_value Node_DVR_Init(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
    NAPI_ASSERT(env, argc == 2,
      "Wrong number of arguments. Expects a single argument.");

    napi_valuetype valuetype0, valuetype1;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
    NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
    NAPI_ASSERT(env, valuetype0 == napi_function && valuetype1 == napi_function,
      "Wrong type of arguments. Expects two functions as first two arguments.");

    //创建异步回调句柄
    napi_value async_name;
    NAPI_CALL(env, napi_create_string_utf8(env, "HCNetSDK video callback", NAPI_AUTO_LENGTH, &async_name));
    NAPI_CALL(env, napi_create_threadsafe_function(env, 
                                                args[0],       //js_cb_function
                                                NULL,          //optional resource
                                                async_name,    //resource name
                                                0,             //Maximum size of the queue. 0 for no limit.
                                                2,             //The initial number of threads
                                                NULL,          //Data to be passed to thread_finalize_cb
                                                final_fuc,     //Function to call when the napi_threadsafe_function is being destroyed.
                                                NULL,          //Optional data to attach to the resulting napi_threadsafe_function
                                                call_js,       //call_js_cb
                                                &ts_fn));

    // 创建结束回调函数引用
    NAPI_CALL(env, napi_create_reference(env, args[1], 1, &cb_function));

    // SDK初始化
    if(!NET_DVR_Init()) {
        printf("初始化sdk失败");
    }
    NET_DVR_SetExceptionCallBack_V30(0, NULL,g_ExceptionCallBack, NULL);
    return NULL;
}

/***
 * function Node_DVR_Cleanup()
 * return null
 * */
static napi_value Node_DVR_Cleanup(napi_env env, napi_callback_info info) {
    // 清理SDK
    NET_DVR_Cleanup();
    // 清理异步回调句柄
    NAPI_CALL(env, napi_release_threadsafe_function(ts_fn, napi_tsfn_abort));
    return NULL;
}

/**
 * function Node_DVR_Login(ip, port, user, pwd)
 * ip: string 设备的ip
 * port: number 端口号
 * user: string 登录用户名
 * pwd: string 登录密码
 * return: number 登录成功返回的句柄。失败返回<0
 * */
static napi_value Node_DVR_Login(napi_env env, napi_callback_info info) {
    size_t argc = 5;          //参数个数
    napi_value args[5];       //参数内容
    char dev_ip[20] = {0};    //设备IP
    int64_t dev_port = 0;     //设备端口
    char dev_user[20] = {0};  //用户名
    char dev_pwd[20] = {0};   //密码
	size_t result;

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
    napi_get_value_string_utf8(env, args[0], dev_ip, 20, &result);
    napi_get_value_int64(env, args[1], &dev_port);
    napi_get_value_string_utf8(env, args[2], dev_user, 20, &result);
    napi_get_value_string_utf8(env, args[3], dev_pwd, 20, &result);

    // 登录设备
    LONG lUserID = -1;
    NET_DVR_DEVICEINFO_V30 struDeviceInfo;
    lUserID = NET_DVR_Login_V30(dev_ip, (WORD)dev_port, dev_user, dev_pwd, &struDeviceInfo);
    if(lUserID < 0) {
        printf("NET_DVR_Login_V30 failed ip:%s", dev_ip);
    }

    napi_value ret;
    NAPI_CALL(env, napi_create_int64(env, lUserID, &ret));
    return ret;
}

/**
 * function Node_DVR_Logout(userID)
 * userID: number 登录的句柄
 * return: bool false失败， true成功
 * */
static napi_value Node_DVR_Logout(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1];
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
    int64_t userID = -1; //登录句柄
    napi_get_value_int64(env, args[0], &userID);

    BOOL bRet = NET_DVR_Logout_V30((LONG)userID);
    
    napi_value ret;
    NAPI_CALL(env, napi_get_boolean(env, bRet==TRUE, &ret));
    return ret;
}

/**
 * function Node_DVR_RealPlay(userID, model, mode)
 * userID: number 登录的句柄
 * model: number 视频通道号
 * mode: number 使用何种码流,1:主码流，其他:子码流
 * return: number 视频播放句柄 <0失败
 * */
static napi_value Node_DVR_RealPlay(napi_env env, napi_callback_info info) {
    size_t argc = 5;
    napi_value args[5];
    int64_t userID = -1; //登录句柄
    int64_t model = -1;  //视频通道号
    int64_t mode = 0;   //使用何种码流,1:主码流，其他:子码流
    
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
    napi_get_value_int64(env, args[0], &userID);
    napi_get_value_int64(env, args[1], &model);
    napi_get_value_int64(env, args[2], &mode);

    // 播放码流
    NET_DVR_CLIENTINFO ClientInfo = {0};
    ClientInfo.hPlayWnd = NULL;         //需要SDK解码时句柄设为有效值，仅取流不解码时可设为空
    ClientInfo.lChannel = (LONG)model;  //预览通道号
    if(mode == 1)
        ClientInfo.lLinkMode    = 0X00000000;       //最高位(31)为0表示主码流，为1表示子码流0～30位表示连接方式：0－TCP方式；1－UDP方式；2－多播方式；3－RTP方式;
    else
        ClientInfo.lLinkMode    = 0X80000000;       //最高位(31)为0表示主码流，为1表示子码流0～30位表示连接方式：0－TCP方式；1－UDP方式；2－多播方式；3－RTP方式;
    ClientInfo.sMultiCastIP = NULL;   //多播地址，需要多播预览时配置

    LONG lRealPlayHandle = -1;
    napi_value retErr;
    NAPI_CALL(env, napi_create_int64(env, lRealPlayHandle, &retErr));

    lRealPlayHandle = NET_DVR_RealPlay_V30((LONG)userID, &ClientInfo, NULL, NULL, FALSE);
    if (lRealPlayHandle < 0)
    {
        printf("NET_DVR_RealPlay_V30 error\n");
        return  retErr;
    }
    if (!NET_DVR_SetRealDataCallBack(lRealPlayHandle, g_RealDataCallBack_V30, (DWORD)lRealPlayHandle))
    {
        printf("NET_DVR_SetRealDataCallBack error\n");
        return retErr;
    }

    napi_value ret;
    NAPI_CALL(env, napi_create_int64(env, lRealPlayHandle, &ret));
    return ret;
}

/**
 * function Node_DVR_RealStop(playHandle)
 * playHandle: number 播放句柄
 * return: number 0失败 1成功
 * */
static napi_value Node_DVR_RealStop(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1];
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
    int64_t playHandle = -1; //播放句柄
    napi_get_value_int64(env, args[0], &playHandle);

    BOOL bRet = NET_DVR_StopRealPlay((LONG)playHandle);
    
    napi_value ret;
    NAPI_CALL(env, napi_create_int32(env, (int32_t)bRet, &ret));
    return ret;
}


static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_PROPERTY("Node_DVR_Init",     Node_DVR_Init),
        DECLARE_NAPI_PROPERTY("Node_DVR_Cleanup",  Node_DVR_Cleanup),
        DECLARE_NAPI_PROPERTY("Node_DVR_Login",    Node_DVR_Login),
        DECLARE_NAPI_PROPERTY("Node_DVR_RealPlay", Node_DVR_RealPlay),
        DECLARE_NAPI_PROPERTY("Node_DVR_RealStop", Node_DVR_RealStop),
        DECLARE_NAPI_PROPERTY("Node_DVR_Logout",   Node_DVR_Logout),
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));

    return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)